package p04;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.StandardEnvironment;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * AutowiredAnnotationBeanPostProcessor后处理器原理
 * <p>
 * 将spring容器中的后处理器设计认为是模板方法模式。
 * <p>
 * 分层设计的本质是用复杂度换取可维护性，其缺点主要体现在性能、灵活性和开发效率上。
 *
 * @author Chen weishuai
 * @since 2025/7/19
 */
@Slf4j
public class AutowiredAnnotationBeanProcessorPrinciple {

    public static void main(String[] args) throws Throwable {
        // 创建一个原生的BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        /**
         * 注册一个bean
         * registerBeanDefinition比较麻烦，这里直接用registerSingleton注册单例bean
         * 这种方法注册的bean，spring会认为提供的就是一个成品bean，就不会走生命周期的那些方法了
         */
        beanFactory.registerSingleton("bean2", new Bean2());
        // 处理@Value进行值注入(与AutowiredAnnotationBeanPostProcessor配合)
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        // ${}解析器，从环境对象中获取数据
        beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
        /**
         *
         *   实例化 --> 依赖注入 ---> 初始化 ---> 销毁
         * AutowiredAnnotationBeanPostProcessor主要作用就是发现对象中的带有Autowired和@Value的方法和属性，
         * 并从beanFactory中找到bean进行注入
         *  processor.postProcessProperties(null, bean1, "bean1");该方法就是解析bean1的方法和属性，并进行注入，分为两步：
         *      1.找到该对象中带有Autowired和@Value的方法和属性，封装进InjectionMetadata对象
         *      2.调用InjectionMetadata的inject方法，利用反射进行注入
         * 容器在bean创建过程中的依赖注入接口，就委托给AutowiredAnnotationBeanPostProcessor来进行注入操作
         * 下面把AutowiredAnnotationBeanPostProcessor单拎出来调用其内部方法，看看它是怎么工作的
         */
        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        // 给AutowiredAnnotationBeanPostProcessor指定beanFactory，将来从这里找到bean进行注入
        processor.setBeanFactory(beanFactory);
        // 这里直接创建bean1，然后让AutowiredAnnotationBeanPostProcessor来解析这个bean1， 并从容器中找到对应的bean来给bean1进行依赖注入
        Bean1 bean1 = new Bean1();
        /**
         * postProcessProperties：能够解析对象中所有带有@Autowired和@Value的方法和属性，并从beanFactory中找对应的bean进行依赖注入
         * postProcessProperties方法参数解释：
         *  第一个参数:不为空表示指定一个值赋给bean1对象的属性，为空表示他自己去容器找bean来进行注入
         *  第二个参数：被注入的目标对象
         *  第三个参数：被注入的目标bean名字(这里没什么用)
         *
         */
        processor.postProcessProperties(null, new Bean1(), "bean1");
        //此时发现@Value和@Autowired都被成功解析了
        System.out.println(bean1);
        System.out.println("-------------核心方法展示-------------");
        /**
         * 上面演示了AutowiredAnnotationBeanPostProcessor工作的核心代码，下面介绍postProcessProperties方法
         * postProcessProperties方法本质就是bean后处理器的依赖注入阶段回调的方法
         * 下面几行代码就是postProcessProperties方法的内部代码
         *    1.InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs):
         *      找到标注了@Autowired和@Value的方法和属性
         *    2.metadata.inject(bean, beanName, pvs):通过反射，真正给属性赋值或者给方法参数并且调用标注了注解的方法
         */
        // 重新创建Bean1对象来演示postProcessProperties的内部逻辑
        Bean1 b1 = new Bean1();
        // 1.获取Bean1类里面所有添加了Autowired和@Value的方法和属性，封装进InjectionMetadata，findAutowiringMetadata是私有的
        Method method = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata",
                String.class, Class.class, PropertyValues.class);
        method.setAccessible(true);
        // 得到 InjectionMetadata 对象，里面封装了所有带有@Autowired和@Value的方法和属性
        InjectionMetadata metadata = (InjectionMetadata) method.invoke(processor, "b1", Bean1.class, null);
        // 2.通过反射注入bean到b对象中,b中对应的方法会被执行，对应的属性会被注入值
        metadata.inject(b1, "b1", null);
        System.out.println(b1);
        System.out.println("-------------inject方法内部做的事情-------------");
        /**
         * 找到属性和方法后，是如何根据属性和方法从容器中找到要注入的bean呢?
         * 已经找到加了注解的方法和属性，spring将这些属性和方法上需要注入参数封装成DependencyDescriptor对象
         * 然后利用DependencyDescriptor从容器中找到bean
         * 下面是inject方法内部做的事情
         */
        // bean2属性就是期待被依赖注入的属性
        Field bean2 = Bean1.class.getField("bean2");
        // 将属性对象封装成一个DependencyDescriptor对象，第二个参数表示从beanFactory没找到是否报错
        DependencyDescriptor dependency1 = new DependencyDescriptor(bean2, false);
        // 利用DependencyDescriptor从容器中找到bean,然后找到的对象o就被赋值给Bean1的bean2属性
        // 如何找的呢?根据bean2的类型从容器中找到此类型的bean，如果有多个则再次筛选出bean名称和属性名称一样的bean
        Object o = beanFactory.doResolveDependency(dependency1, null, null, null);
        // 从容器中找到了bean2，打印出来
        System.out.println(o);

        // 如何找到方法上的bean呢？以Bean1的setHome方法为例
        Method m = Bean1.class.getMethod("setHome", String.class);
        // MethodParameter对象第一个参数是方法对象，第二个参数是该方法哪个参数需要进行bean注入
        DependencyDescriptor descriptor2 = new DependencyDescriptor(new MethodParameter(m, 0), false);
        // 利用DependencyDescriptor从容器中找到bean(@Value注入要配合其他组件才能从环境中获取数据)，这种查找可比上面的复杂多了
        Object o2 = beanFactory.doResolveDependency(descriptor2, null, null, null);
        // 从容器中找到了依赖对象，打印出来
        System.out.println(o2);
        /**
         * 这样依次循环方法所有参数，得到所有bean，然后反射调用该方法，不就完成了方法的依赖注入了吗?
         * 由此可见，AutowiredAnnotationBeanPostFactory Bean后处理器本质就是反射的层层封装
         */

    }

    @Data
    @Slf4j
    static class Bean1 {
        @Autowired
        public Bean2 bean2;

        @Autowired
        public void setBean2(Bean2 bean2) {
            log.warn("依赖注入@Autowired：{}", bean2);
        }

        @Autowired
        public void setHome(@Value("${JAVA_HOME}") String home) {
            log.warn("@Value生效：{}", home);
        }

        //这个方法无法被调用，因为这节的AutowiredAnnotationBeanPostProcessor无法解析@Resource
        @Resource
        public void resource(Bean2 bean2) {
            log.warn("bean1依赖注入@Resource :{}", bean2);
        }
    }


    @Data
    static class Bean2 {
        private String name = "bean2";
    }


}
